using System;
using System.Collections.Generic;
using System.Text;

namespace DesignByContract
{

    using System.Configuration;

    #region App settings helper

    public enum Required { Yes, No }

    public enum GuardedParse { Yes, No }

    /// <summary>
    /// Singleton configuration helper for ConfigurationManager.AppSettings
    /// </summary>
    public static class AppSettingsHelper {

        /// <summary>
        /// TODO: Strengthen type return - have to figure out the best way..
        /// </summary>
        /// <param name="?"></param>
        /// <returns></returns>
        private delegate object ValueParser(string pKey, string pValue, GuardedParse pGuarded);
        private static readonly Dictionary<Type, ValueParser>  mParserDelegates = new Dictionary<Type, ValueParser>();
        private static readonly GuardedParse mGuidedParseDefault = GuardedParse.No;

        /// <summary>
        /// Sets up delegates for value parsing; there must be a more adaptive manner of
        /// doing this.....
        /// </summary>
        static AppSettingsHelper() {
            mParserDelegates[typeof(uint)] = new ValueParser(UnsignedIntegerParser);
            mParserDelegates[typeof(int)] = new ValueParser(IntegerParser);
            mParserDelegates[typeof(bool)] = new ValueParser(BooleanParser);
            mParserDelegates[typeof(string)] = new ValueParser(StringParser);
            // TODO: Dodgy - but set the default for debug efforts to aid, well, debugging
#if DEBUG
            mGuidedParseDefault = GuardedParse.Yes;
#endif
        }

        private static string SanityCheck(string pKey, Required pMandatory) {
            return SanityCheck(pKey, pMandatory, null);
        }

        private static string SanityCheck(string pKey, Required pMandatory, string pDefault) {
            DBC.Assert(pKey != null, "Null configuration key used!");
            string val = ConfigurationManager.AppSettings[pKey];
            DBC.Assert(val != null || pMandatory == Required.No || pDefault != null, 
                        String.Format("Configuration value required but value absent ({0})", pKey));
            return val ?? pDefault;
        }

        private static ValueParser GetParser<T>() {
                Type targetType = typeof (T);
                DBC.Assert(mParserDelegates.ContainsKey(targetType),
                           String.Format("No parser for type {0}", targetType.FullName));
                return mParserDelegates[targetType];
        }

        /// <summary>
        /// Return the noted app setting as type T; throw an exception if absent from configuration
        /// </summary>
        /// <param name="pKey"></param>
        /// <returns></returns>
        public static T Retrieve<T>(string pKey) {
            return Retrieve<T>(pKey, Required.Yes);
        }

        /// <summary>
        /// Return the noted app setting as type T; throw an exception if absent from configuration; 
        /// plus, attempt a guarded parse possibly
        /// </summary>
        /// <param name="pKey"></param>
        /// <returns></returns>
        public static T Retrieve<T>(string pKey, GuardedParse pGuarded) {
            return Retrieve<T>(pKey, Required.Yes, pGuarded);
        }

        /// <summary>
        /// Return the noted app setting as type T; substitute the default value 
        /// if absent from configuration
        /// </summary>
        /// <param name="pKey"></param>
        /// <returns></returns>
        public static T Retrieve<T>(string pKey, T pDefault) {
            return Retrieve(pKey, Required.No, pDefault, mGuidedParseDefault);
        }

        /// <summary>
        /// Return the noted app setting as type T throwing or suppressing
        /// an exception based on the 'Required' setting
        /// </summary>
        /// <param name="pKey"></param>
        /// <returns></returns>
        public static T Retrieve<T>(string pKey, Required pMandatory) {
            return Retrieve(pKey, pMandatory, default(T), mGuidedParseDefault);
        }

        /// <summary>
        /// Return the noted app setting as type T throwing or suppressing
        /// an exception based on the 'Required' setting
        /// </summary>
        /// <param name="pKey"></param>
        /// <returns></returns>
        public static T Retrieve<T>(string pKey, Required pMandatory, GuardedParse pGuarded) {
            return Retrieve(pKey, pMandatory, default(T), pGuarded);
        }

        /// <summary>
        /// Return the noted app setting as type T throwing or suppressing
        /// an exception based on the 'Required' setting, using the default if absent
        /// </summary>
        /// <param name="pKey"></param>
        /// <returns></returns>
        private static T Retrieve<T>(string pKey, Required pMandatory, T pDefault, GuardedParse pGuarded) {
            string val = SanityCheck(pKey, pMandatory);
            return val == null ? pDefault : (T) GetParser<T>()(pKey, val, pGuarded);
        }

        #region specific type parsers
        // TODO: Could consider using the Converter<S,T>.CreateDelegate(...) here or TypeConverter sub classes

        private static object StringParser(string pKey, string pValue, GuardedParse pGuarded) {
            return pValue;   
        }

        private static object BooleanParser(string pKey, string pValue, GuardedParse pGuarded) {
            bool ret = false;
            if (pGuarded == GuardedParse.No)
                ret = bool.Parse(pValue);
            else
                DBC.Assert(bool.TryParse(pValue, out ret), GuardedParseMessage("bool", pValue, pKey));
            return ret;
        }

        private static object IntegerParser(string pKey, string pValue, GuardedParse pGuarded) {
            int ret = 0;
            if (pGuarded == GuardedParse.No)
                ret = int.Parse(pValue);
            else
                DBC.Assert(int.TryParse(pValue, out ret), GuardedParseMessage("int", pValue, pKey));
            return ret;
        }

        private static object UnsignedIntegerParser(string pKey, string pValue, GuardedParse pGuarded) {
            uint ret = 0;
            if (pGuarded == GuardedParse.No)
                ret = uint.Parse(pValue);
            else
                DBC.Assert(uint.TryParse(pValue, out ret), GuardedParseMessage("uint", pValue, pKey));
            return ret;
        }

        
        private static string GuardedParseMessage(string pType, string pValue, string pKey) {
            return String.Format("Cannot parse as type \"{0}\" the value \"{1}\" for configuration key \"{2}\"", pType, pValue, pKey);
        }

        #endregion
    }

    #endregion
}
